package com.ohosadvance.topsnackbar;

import com.ohosadvance.topsnackbar.utils.LogUtil;
import com.ohosadvance.topsnackbar.utils.PixelUtil;
import com.ohosadvance.topsnackbar.utils.TextUtils;

import ohos.agp.animation.Animator;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.LayoutScatter;
import ohos.agp.components.Text;
import ohos.agp.components.Button;
import ohos.agp.components.StackLayout;
import ohos.agp.components.DependentLayout;
import ohos.agp.components.ComponentParent;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.utils.Color;

import ohos.app.Context;

import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;

import ohos.global.resource.NotExistException;

import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Rect;
import ohos.media.image.common.Size;

import java.io.IOException;
import java.io.InputStream;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.SoftReference;

/**
 * description : TSnackBar
 *
 * @since : 2021/6/30
 */
public final class TSnackbar {
    /**
     * TSnackbar Callback
     */
    public abstract static class Callback {
        /**
         * DISMISS_EVENT_SWIPE
         */
        public static final int DISMISS_EVENT_SWIPE = 0;

        /**
         * DISMISS_EVENT_ACTION
         */
        public static final int DISMISS_EVENT_ACTION = 1;

        /**
         * DISMISS_EVENT_TIMEOUT
         */
        public static final int DISMISS_EVENT_TIMEOUT = 2;

        /**
         * DISMISS_EVENT_MANUAL
         */
        public static final int DISMISS_EVENT_MANUAL = 3;

        /**
         * DISMISS_EVENT_CONSECUTIVE
         */
        public static final int DISMISS_EVENT_CONSECUTIVE = 4;

        @Retention(RetentionPolicy.SOURCE)
        public @interface DismissEvent {
        }

        /**
         * onDismissed
         *
         * @param snackbar snackbar
         * @param event event
         */
        public void onDismissed(TSnackbar snackbar, @DismissEvent int event) {
        }

        /**
         * onShown
         *
         * @param snackbar snackbar
         */
        public void onShown(TSnackbar snackbar) {
        }
    }

    /**
     * LENGTH_INDEFINITE
     */
    public static final int LENGTH_INDEFINITE = -2;

    /**
     * LENGTH_SHORT
     */
    public static final int LENGTH_SHORT = -1;

    /**
     * LENGTH_LONG
     */
    public static final int LENGTH_LONG = 0;
    private static final int ANIMATION_DURATION = 250;
    private static final int ANIMATION_FADE_DURATION = 180;
    private static final int MSG_SHOW = 0;
    private static final int MSG_DISMISS = 100;

    private final ComponentContainer mParent;
    private final Context mContext;
    private final SnackbarLayout mView;
    private int mDuration;
    private Callback mCallback;
    private int mViewHeight;
    private MyEventHandler sHandler = new MyEventHandler(EventRunner.getMainEventRunner());

    private TSnackbar(ComponentContainer parent) {
        mParent = parent;
        mContext = parent.getContext();
        SoftReference<Context> softReference = new SoftReference<>(mContext);
        PixelUtil.initContext(softReference.get());
        LayoutScatter layoutScatter = LayoutScatter.getInstance(mContext);
        mView = (SnackbarLayout) layoutScatter.parse(ResourceTable.Layout_tsnackbar_layout, mParent, false);
        mView.setOnToucheStateChangeListener(new SnackbarLayout.OnToucheStateChangeListener() {
            @Override
            public void onActionDownAndMove() {
                SnackbarManager.getInstance()
                                        .cancelTimeout(mManagerCallback);
            }

            @Override
            public void onActionUp() {
                SnackbarManager.getInstance()
                                        .restoreTimeout(mManagerCallback);
            }

            @Override
            public void onShouldDismiss() {
                dispatchDismiss(Callback.DISMISS_EVENT_SWIPE);
            }
        });
        mView.setMessageView((Text) mView.findComponentById(ResourceTable.Id_snackbar_text));
        mView.setActionView((Button) mView.findComponentById(ResourceTable.Id_snackbar_action));
    }

    /**
     * make a TSnackbar
     *
     * @param component TSnackbar 的父布局
     * @param text TSnackbar 显示的text内容
     * @param duration TSnackbar 显示的时长
     * @return TSnackbar
     */
    public static TSnackbar make(Component component, CharSequence text, int duration) {
        TSnackbar snackbar = new TSnackbar(findSuitableParent(component));
        snackbar.setText(text);
        snackbar.setDuration(duration);
        return snackbar;
    }

    /**
     * make a TSnackbar
     *
     * @param component TSnackbar 的父布局
     * @param resId TSnackbar 显示的text内容所在的resourceID
     * @param duration TSnackbar 显示的时长
     * @return TSnackbar
     */
    public static TSnackbar make(Component component, int resId, int duration) {
        return make(component, component.getContext().getString(resId), duration);
    }

    private static ComponentContainer findSuitableParent(Component view) {
        ComponentContainer fallback = null;
        do {
            if (view instanceof StackLayout || view instanceof DependentLayout) {
                return (ComponentContainer) view;
            } else {
                if (view.getComponentParent() instanceof ComponentContainer) {
                    ComponentContainer parent = (ComponentContainer) view.getComponentParent();
                    // check if there's something else beside toolbar
                    if (parent.getChildCount() > 1) {
                        int childrenCnt = parent.getChildCount();
                        int toolbarIdx;
                        for (int i = 0; i < childrenCnt; i++) {
                            // find the index of toolbar in the layout (most likely 0, but who knows)
                            if (parent.getComponentAt(i) == view) {
                                toolbarIdx = i;
                                // check if there's something else after the toolbar in the layout
                                if (toolbarIdx < childrenCnt - 1) {
                                    // try to find some ViewGroup where you can attach the toast
                                    while (i < childrenCnt) {
                                        i++;
                                        Component component = parent.getComponentAt(i);
                                        if (component instanceof ComponentContainer) {
                                            return (ComponentContainer) component;
                                        }
                                    }
                                }
                                break;
                            }
                        }
                    }
                }
            }
            final ComponentParent parent = view.getComponentParent();
            fallback = parent instanceof ComponentContainer ? (ComponentContainer) parent : null;
        } while (fallback != null);

        return fallback;
    }

    /**
     * set Icon Padding
     *
     * @param padding Icon Padding
     * @return TSnackbar
     */
    public TSnackbar setIconPadding(int padding) {
        final Text tv = mView.getMessageView();
        tv.setAroundElementsPadding(padding);
        return this;
    }

    /**
     * set Left Icon
     *
     * @param mediaRes 表示Icon的resourceID
     * @param sizeVp icon 的大小（单位：vp）
     * @return TSnackbar
     */
    public TSnackbar setIconLeft(int mediaRes, float sizeVp) {
        final Text tv = mView.getMessageView();
        PixelMapElement pixelMapElement = (PixelMapElement) fitDrawable(mediaRes, (int) PixelUtil.vp2px((int) sizeVp));
        final Element[] compoundDrawables = tv.getAroundElements();
        tv.setAroundElements(pixelMapElement, compoundDrawables[1], compoundDrawables[2], compoundDrawables[3]);
        return this;
    }

    /**
     * set Right Icon
     *
     * @param mediaRes 表示Icon的resourceID
     * @param sizeVp icon 大小（单位：vp）
     * @return TSnackbar
     */
    public TSnackbar setIconRight(int mediaRes, float sizeVp) {
        final Text tv = mView.getMessageView();
        PixelMapElement pixelMapElement = (PixelMapElement) fitDrawable(mediaRes, (int) PixelUtil.vp2px((int) sizeVp));
        final Element[] elements = tv.getAroundElements();
        tv.setAroundElements(elements[0], elements[1], pixelMapElement, elements[3]);
        return this;
    }

    /**
     * set TSnackbar MaxWidth
     *
     * @param maxWidth TSnackbar MaxWidth
     * @return TSnackbar
     */
    public TSnackbar setMaxWidth(int maxWidth) {
        mView.mMaxWidth = maxWidth;
        return this;
    }

    private Element fitDrawable(int drawableRes, int sizePx) {
        Element resultElement = null;
        InputStream drawableInputStream = null;
        try {
            drawableInputStream = mContext.getResourceManager().getResource(drawableRes);
            ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
            sourceOptions.formatHint = "image/png";
            ImageSource imageSource = ImageSource.create(drawableInputStream, null);
            ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
            PixelMapElement tempPixElement = new PixelMapElement(mContext.getResourceManager()
                    .getResource(drawableRes));
            if (tempPixElement.getWidth() != sizePx || tempPixElement.getHeight() != sizePx) {
                decodingOptions.desiredSize = new Size(0, 0);
            } else {
                decodingOptions.desiredSize = new Size(sizePx, sizePx);
            }
            decodingOptions.desiredRegion = new Rect(0, 0, 0, 0);
            decodingOptions.desiredPixelFormat = PixelFormat.ARGB_8888;
            PixelMap pixelMap = imageSource.createPixelmap(decodingOptions);
            resultElement  = new PixelMapElement(pixelMap);
            resultElement.setBounds(0, 0, sizePx, sizePx);
            drawableInputStream.close();
            return resultElement;
        } catch (IOException | NotExistException e) {
            e.printStackTrace();
        }
        return resultElement;
    }

    /**
     * set TSnackbar Action
     *
     * @param strResId action的文字对应的resourceID
     * @param listener action的点击监听
     * @return TSnackbar
     */
    public TSnackbar setAction(int strResId, Component.ClickedListener listener) {
        return setAction(mContext.getString(strResId), listener);
    }

    /**
     * set TSnackbar Action
     *
     * @param text action的文字
     * @param listener action的点击监听
     * @return TSnackbar
     */
    public TSnackbar setAction(String text, final Component.ClickedListener listener) {
        return setAction(text, true, listener);
    }

    /**
     * set TSnackbar Action
     *
     * @param text action的文字
     * @param shouldDismissOnClick TSnackbar的action点击后是否dismiss
     * @param listener action的点击监听
     * @return TSnackbar
     */
    public TSnackbar setAction(String text, final boolean shouldDismissOnClick,
                                final Component.ClickedListener listener) {
        final Text tv = mView.getActionView();

        if (TextUtils.isEmpty(text) || listener == null) {
            tv.setVisibility(Component.HIDE);
            tv.setClickedListener(null);
        } else {
            tv.setVisibility(Component.VISIBLE);
            tv.setText(text);
            tv.setClickedListener(component -> {
                listener.onClick(component);
                if (shouldDismissOnClick) {
                    dispatchDismiss(Callback.DISMISS_EVENT_ACTION);
                }
            });
        }
        return this;
    }

    /**
     * 设置action text color
     *
     * @param resColor Resource color name
     * @return TSnackbar
     */
    public TSnackbar setActionTextColor(int resColor) {
        final Text tv = mView.getActionView();
        tv.setTextColor(new Color(mContext.getColor(resColor)));
        return this;
    }

    /**
     * 设置 TSnackbar 显示的文字内容
     *
     * @param message TSnackbar 显示的文字内容
     * @return TSnackbar
     */
    public TSnackbar setText(CharSequence message) {
        final Text tv = mView.getMessageView();
        tv.setText(message.toString());
        return this;
    }


    /**
     * 设置 TSnackbar 显示的文字内容
     *
     * @param resId Resource string name
     * @return TSnackbar
     */
    public TSnackbar setText(int resId) {
        return setText(mContext.getString(resId));
    }

    /**
     * 设置 TSnackbar 显示时长
     *
     * @param duration TSnackbar显示时长
     * @return TSnackbar
     */
    public TSnackbar setDuration(int duration) {
        mDuration = duration;
        return this;
    }

    /**
     * 获取 TSnackbar 显示时长
     *
     * @return TSnackbar duration
     */
    public int getDuration() {
        return mDuration;
    }

    /**
     * 获取 TSnackbar 内的 SnackbarLayout
     *
     * @return TSnackbar内的SnackbarLayout
     */
    public Component getView() {
        return mView;
    }

    /**
     * show TSnackbar
     */
    public void show() {
        SnackbarManager.getInstance()
                .show(mDuration, mManagerCallback);
    }

    /**
     * dismiss TSnackbar
     */
    public void dismiss() {
        dispatchDismiss(Callback.DISMISS_EVENT_MANUAL);
    }

    private void dispatchDismiss(@Callback.DismissEvent int event) {
        SnackbarManager.getInstance()
                .dismiss(mManagerCallback, event);
    }

    /**
     * set Callback
     *
     * @param callback callback
     * @return TSnackbar
     */
    public TSnackbar setCallback(Callback callback) {
        mCallback = callback;
        return this;
    }

    /**
     * 获取 TSnackbar 是否在显示
     *
     * @return true : 显示 ; false : 没显示
     */
    public boolean isShown() {
        return SnackbarManager.getInstance()
                .isCurrent(mManagerCallback);
    }

    /**
     * 获取 TSnackbar 是否在显示或即将显示
     *
     * @return true : 显示或即将显示 ; false : 没显示且不在显示队列
     */
    public boolean isShownOrQueued() {
        return SnackbarManager.getInstance()
                .isCurrentOrNext(mManagerCallback);
    }

    private final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() {
        @Override
        public void show() {
            sHandler.sendEvent(InnerEvent.get(MSG_SHOW, TSnackbar.this));
        }

        @Override
        public void dismiss(int event) {
            sHandler.sendEvent(InnerEvent.get(MSG_DISMISS, event, TSnackbar.this));
        }
    };

    void showView() {
        if (mView.getComponentParent() == null) {
            mView.setLayoutRefreshedListener(component -> {
                mView.setLayoutRefreshedListener(null);
                mViewHeight = component.getHeight();
                if (mView.mMaxWidth > 0 && component.getWidth() > mView.mMaxWidth) {
                    ComponentContainer.LayoutConfig layoutConfig = mView.getLayoutConfig();
                    layoutConfig.width = mView.mMaxWidth;
                    mView.setLayoutConfig(layoutConfig);
                }
                animateViewIn();
            });
            mParent.addComponent(mView);
            mView.setTranslationY(-1000);
        }
        mView.setOnAttachStateChangeListener(new SnackbarLayout.OnAttachStateChangeListener() {
            @Override
            public void onViewAttachedToWindow(Component component) {
                LogUtil.loge("onViewAttachedToWindow");
            }

            @Override
            public void onViewDetachedFromWindow(Component component) {
                LogUtil.loge("onViewDetachedFromWindow");
                if (isShownOrQueued()) {
                    sHandler.postTask(new Runnable() {
                        @Override
                        public void run() {
                            onViewHidden(Callback.DISMISS_EVENT_MANUAL);
                        }
                    });
                }
            }
        });
    }

    private void animateViewIn() {
        mView.createAnimatorProperty()
                .moveFromY(-mViewHeight)
                .moveToY(0)
                .setDuration(ANIMATION_DURATION)
                .setStateChangedListener(new Animator.StateChangedListener() {
                    @Override
                    public void onStart(Animator animator) {
                        mView.animateChildrenIn(ANIMATION_DURATION - ANIMATION_FADE_DURATION,
                                ANIMATION_FADE_DURATION);
                    }

                    @Override
                    public void onStop(Animator animator) {
                        if (mCallback != null) {
                            mCallback.onShown(TSnackbar.this);
                        }
                        SnackbarManager.getInstance()
                                .onShown(mManagerCallback);
                    }

                    @Override
                    public void onCancel(Animator animator) {}

                    @Override
                    public void onEnd(Animator animator) {}

                    @Override
                    public void onPause(Animator animator) {}

                    @Override
                    public void onResume(Animator animator) {}
                }).start();
    }

    private void animateViewOut(final int event) {
        mView.createAnimatorProperty()
                .moveFromY(0)
                .moveToY(-mViewHeight)
                .setDuration(ANIMATION_DURATION)
                .setStateChangedListener(new Animator.StateChangedListener() {
                    @Override
                    public void onStart(Animator animator) {
                        mView.animateChildrenOut(0, ANIMATION_FADE_DURATION);
                    }

                    @Override
                    public void onStop(Animator animator) {}

                    @Override
                    public void onCancel(Animator animator) {}

                    @Override
                    public void onEnd(Animator animator) {
                        onViewHidden(event);
                    }

                    @Override
                    public void onPause(Animator animator) {}

                    @Override
                    public void onResume(Animator animator) {}
                }).start();
    }

    void hideView(int event) {
        if (mView.getVisibility() != Component.VISIBLE || mView.isBeingDragged()) {
            onViewHidden(event);
        } else {
            animateViewOut(event);
        }
    }

    private void onViewHidden(int event) {
        SnackbarManager.getInstance()
                .onDismissed(mManagerCallback);

        if (mCallback != null) {
            mCallback.onDismissed(this, event);
        }

        final ComponentContainer parent = (ComponentContainer) mView.getComponentParent();
        if (parent instanceof ComponentContainer) {
            ((ComponentContainer) parent).removeComponent(mView);
        }
    }

    static class MyEventHandler extends EventHandler {
        MyEventHandler(EventRunner runner) {
            super(runner);
        }

        @Override
        protected void processEvent(InnerEvent event) {
            super.processEvent(event);
            switch (event.eventId) {
                case MSG_SHOW:
                    ((TSnackbar) event.object).showView();
                    break;
                case MSG_DISMISS:
                    ((TSnackbar) event.object).hideView((int) event.param);
                    break;
            }
        }
    }
}
